package com.like.bluetooth.classic.client.connect.executor

import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import com.like.bluetooth.exception.BluetoothExceptionBusy
import com.like.bluetooth.exception.BluetoothExceptionCancelTimeout
import com.like.bluetooth.exception.toBluetoothException
import com.like.bluetooth.util.BluetoothPermissionUtils
import com.like.bluetooth.util.MutexUtils
import com.like.bluetooth.util.SuspendCancellableCoroutineWithTimeout
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicLong
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * 蓝牙连接及数据操作的前提条件
 * 包括：并发处理、超时处理、蓝牙相关的前置条件检查、错误处理。
 */
internal abstract class BaseConnectExecutor(context: Context, address: String?) :
    AbstractConnectExecutor(context, address) {
    private val connectMutexUtils = MutexUtils()
    private val connectedMutexUtils = MutexUtils()
    private val suspendCancellableCoroutineWithTimeout by lazy {
        SuspendCancellableCoroutineWithTimeout()
    }

    // 重连时间间隔
    private val autoConnectInterval = AtomicLong(0)

    // 是否自动重连
    private var autoConnect = false

    @SuppressLint("MissingPermission")
    final override fun connect(
        scope: CoroutineScope,
        autoConnectInterval: Long,
        timeout: Long,
        onConnected: () -> Unit,
        onDisconnected: ((Throwable) -> Unit)?
    ) {
        if (autoConnect) {
            onDisconnected?.invoke(BluetoothExceptionBusy("当前是自动重连模式，请勿重复操作！"))
            return
        }
        autoConnect = autoConnectInterval > 0L
        autoConnect(scope, autoConnectInterval, timeout, onConnected, onDisconnected)
    }

    private fun autoConnect(
        scope: CoroutineScope,
        autoConnectInterval: Long,
        timeout: Long,
        onConnected: () -> Unit,
        onDisconnected: ((Throwable) -> Unit)?
    ) {
        Log.i("BaseConnectExecutor", "开始连接 $address")
        scope.launch(Dispatchers.IO) {
            // 释放锁。否则会有可能出现问题：比如此时正在设置通知，然后连接就会由于锁被占用无法执行，并抛出 BleExceptionBusy，然后造成自动重连中断。
            suspendCancellableCoroutineWithTimeout.cancel()
            doConnect(timeout, onConnected = {
                Log.i("BaseConnectExecutor", "连接成功 $address")
                scope.launch(Dispatchers.Main) {
                    onConnected()
                }
            }) {
                Log.e("BaseConnectExecutor", "连接失败 $address $it")
                // 如果外层的scope被取消了，那么这里也无法调用 launch 方法了。
                // 所以我们使用 autoConnect 标记来取消重连
                scope.launch(Dispatchers.Main) {
                    onDisconnected?.invoke(it)
                    if (it !is BluetoothExceptionCancelTimeout) {
                        // 释放锁，释放蓝牙相关的资源。避免无法连接。
                        doDisconnect()
                        if (autoConnect) {
                            Log.i("BaseConnectExecutor", "准备延迟 $autoConnectInterval 毫秒后开始重连 $address")
                            withContext(Dispatchers.IO) {
                                delay(autoConnectInterval)
                            }
                            autoConnect(scope, autoConnectInterval, timeout, onConnected, onDisconnected)
                        }
                    }
                }
            }
        }
    }

    private suspend fun doConnect(
        timeout: Long, onConnected: () -> Unit, onDisconnected: ((Throwable) -> Unit)?
    ) {
        try {
            BluetoothPermissionUtils.checkConnectEnvironmentOrThrow(mContext)
            connectMutexUtils.withTryLockOrThrow("正在建立连接，请稍后！") {
                withContext(Dispatchers.IO) {
                    suspendCancellableCoroutineWithTimeout.execute(
                        timeout, "连接蓝牙设备超时：$address"
                    ) { continuation ->
                        continuation.invokeOnCancellation {
                            doDisconnect()
                        }
                        onConnect(this, onSuccess = {
                            onConnected()
                            if (continuation.isActive) continuation.resume(Unit)
                        }) {
                            if (continuation.isActive) {
                                continuation.resumeWithException(it)
                            } else {
                                onDisconnected?.invoke(it)
                            }
                        }
                    }
                }
            }
        } catch (e: Exception) {
            onDisconnected?.invoke(e.toBluetoothException())
        }
    }

    final override fun disconnect() {
        autoConnectInterval.set(0)
        autoConnect = false// 取消重连
        doDisconnect()
    }

    private fun doDisconnect() {
        try {
            // 此处如果不取消，那么还会把超时错误传递出去的。
            suspendCancellableCoroutineWithTimeout.cancel()
            if (BluetoothPermissionUtils.checkConnectEnvironment(mContext)) {
                onDisconnect()
            }
        } catch (e: Exception) {
            throw e.toBluetoothException()
        }
    }

    /**
     * 封装连接状态的其它操作步骤。
     */
    private suspend inline fun <T> connectedExecute(
        busyMessage: String?, timeout: Long, timeoutErrorMsg: String = "", crossinline block: (CancellableContinuation<T>) -> Unit
    ): T = try {
        BluetoothPermissionUtils.checkConnectEnvironmentOrThrow(mContext)
        // withTryLock 方法会一直持续到命令执行完成或者 suspendCancellableCoroutineWithTimeout 超时，这段时间是一直上锁了的，
        // 所以不会产生 BleExceptionBusy 异常。
        connectedMutexUtils.withTryLockOrThrow(busyMessage) {
            withContext(Dispatchers.IO) {
                suspendCancellableCoroutineWithTimeout.execute(timeout, timeoutErrorMsg) {
                    block(it)
                }
            }
        }
    } catch (e: Exception) {
        // 转换一下异常，方便使用者判断。
        throw e.toBluetoothException()
    }

    override suspend fun write(data: ByteArray, timeout: Long) =
        connectedExecute("正在写数据……", timeout, "写数据超时") { continuation ->
            onWrite(data, onSuccess = {
                if (continuation.isActive) continuation.resume(Unit)
            }) {
                if (continuation.isActive) continuation.resumeWithException(it)
            }
        }

    override fun setReadCallback(): Flow<ByteArray> = callbackFlow {
        onSetReadCallback(
            onResult = {
                trySend(it)
            }
        )
        // 注意：不能在这个作用域里面使用挂起函数，这样会导致使用者使用 cancel 方法关闭协程作用域的时候，
        // 因为还没有执行到 awaitClose 方法，所以就触发不了 awaitClose 里面的代码。所以如果要使用挂起函数，有两种方式：
        // 1、使用 launch 方法重新开启一个子协程。
        // 2、把挂起函数 try catch 起来，这样就能捕获 JobCancellationException 异常，然后就可以执行下面的 awaitClose 方法。
        awaitClose {
            onRemoveReadCallback()
            Log.d("BaseConnectExecutor", "读数据回调被取消")
        }
    }

    override fun close() {
        super.close()
        disconnect()
        ConnectExecutorFactory.remove(address)
        onRemoveReadCallback()
    }

    final override fun onBleOff() {
        super.onBleOff()
        // 在 connect 的时候关闭蓝牙开关，此时如果不调用 doDisconnect 方法，那么就不会清空回调（参考ConnectExecutor.onDisconnect），则会继续发送 connect 方法的相关错误，造成 UI 显示错乱。
        doDisconnect()
        // 如果蓝牙开关关闭后重新连接失败，那么可以重新扫描，然后再重新连接就能成功。但是这个要看外围设备是否支持，有的不需要重新扫描就能重连成功。
    }

    protected abstract fun onConnect(
        scope: CoroutineScope, onSuccess: (() -> Unit)?, onError: ((Throwable) -> Unit)?
    )

    protected abstract fun onDisconnect()
    protected abstract fun onWrite(
        data: ByteArray,
        onSuccess: (() -> Unit)? = null,
        onError: ((Throwable) -> Unit)? = null
    )

    protected abstract fun onSetReadCallback(onResult: (ByteArray) -> Unit)

    protected abstract fun onRemoveReadCallback()

}
